#include <iostream>
#include <fstream>
#include "libusb.h"
// #include "iniparser.h"
#include "tinyxml2.h"

#define MEM_TYPE_IFLASH     1
#define MEM_TYPE_EXTFLASH   2
#define MEM_TYPE_RAM        3
#define MEM_TYPE_CARD       4
#define MEM_TYPE_MAX        5

#define CMD_TYPE_WRITE      1
#define CMD_TYPE_READ       2
#define CMD_TYPE_JUMP       3
#define CMD_TYPE_MAX        4

using namespace std;
//using namespace tinyxml2;

typedef struct
{
    uint8_t cmd;
    uint8_t memType;
    uint16_t reserve00;
    uint32_t memAddr;
    uint32_t size;
}_S_Commond;

_S_Commond gsCmd __attribute__ ((aligned (4))) ;


libusb_device_handle *hUsbDevice;
uint8_t iend,oend;

int usbDevice_sendCommond(_S_Commond *sCmd,unsigned char *retInfoAddr,uint32_t size);
int usbDevice_sendData(unsigned char *buff,uint32_t size);

int main(int argc, char const *argv[])
{
    unsigned char infoBuffer[512] __attribute__ ((aligned (4))) ;
    string filePath;
    uint16_t pid,vid;
    uint32_t u32,deviceCacheSize,memBlockSize;
    uint32_t i,l,len;
    uint8_t *datBuff ;
    int n;
    char c;
    ifstream inFile;
    ofstream outFile ;
    char kernelDriverDetached = 0;

    pid = 0 ;
    vid = 0 ;
    iend = 0 ;
    oend = 0 ;
    u32 = 0 ;
    filePath.empty();
    memset(&gsCmd,0,sizeof(gsCmd));

    // 解析参数
    for(int i=1; i<argc; i++)
    {
        if(argv[i][0] == '-')
        {
            switch (argv[i][1])
            {
            case 't':
            case 'T':
                gsCmd.memType = atoi(&argv[i][2]);
                break;
            case 'f':
            case 'F':
                filePath = (char *)&argv[i][2];
                break;
            case 'c':
            case 'C':
                gsCmd.cmd = atoi(&argv[i][2]);
                break;
            case 's':
            case 'S':
                gsCmd.size = atoi(&argv[i][2]) ;
                break;
            case 'a':
            case 'A':
                u32 = 0;
                n = 0 ;
                c = argv[i][2+n];
                while (c)
                {
                    u32 <<= 4;
                    if((c <= '9') && (c >= '0'))
                    {
                        u32 |= c - '0' ;
                    }
                    else if((c <= 'f') && (c >= 'a'))
                    {
                        u32 |= c - 'a' + 10 ;
                    }
                    else if((c <= 'f') && (c >= 'A'))
                    {
                        u32 |= c - 'A' + 10 ;
                    }
                    n ++;
                    c = argv[i][2+n];
                }
                gsCmd.memAddr = u32;
                break;
            case 'h':
            case 'H':
            default:
                break;
            }
        }
    }
    if((filePath.length() == 0))
    {
        fprintf(stderr, "Error parameter.\n");
        return 0;
    }
    if((gsCmd.cmd == 0) || (gsCmd.cmd >= CMD_TYPE_MAX))
    {
        fprintf(stderr, "Error connot support commond:%d\n",gsCmd.cmd);
        return 0;
    }
    if((gsCmd.cmd == 0) || (gsCmd.cmd >= MEM_TYPE_MAX))
    {
        fprintf(stderr, "Error connot support memory type:%d\n",gsCmd.cmd);
        return 0;
    }

    //从xml文件读取配置
    tinyxml2::XMLDocument xmlDoc;
    tinyxml2::XMLError xmlErr ;
    xmlErr = xmlDoc.LoadFile("config.xml");
    if(xmlErr != tinyxml2::XML_SUCCESS)
    {
        printf("create xml document\n");
        xmlDoc.InsertFirstChild(xmlDoc.NewDeclaration());
        tinyxml2::XMLElement* root = xmlDoc.NewElement("CONFIG");
        xmlDoc.InsertEndChild(root);
        tinyxml2::XMLElement* usbFather = xmlDoc.NewElement("USB");
        tinyxml2::XMLElement* usbSub = xmlDoc.NewElement("ID");
        usbSub->SetAttribute("PID",0x0801);
        usbFather->InsertFirstChild(usbSub);
        usbSub->SetAttribute("VID",0xF101);
        usbFather->InsertEndChild(usbSub);

        usbSub = xmlDoc.NewElement("PORT");
        usbSub->SetAttribute("IN",2);
        usbFather->InsertFirstChild(usbSub);
        usbSub->SetAttribute("OUT",1);
        usbFather->InsertEndChild(usbSub);

        root->InsertEndChild(usbFather);
        xmlDoc.SaveFile("config.xml");
    }
    tinyxml2::XMLElement* root_usb_id   = xmlDoc.RootElement()->FirstChildElement("USB")->FirstChildElement("ID");
    tinyxml2::XMLElement* root_usb_port = xmlDoc.RootElement()->FirstChildElement("USB")->FirstChildElement("PORT");
    pid = atoi(root_usb_id->Attribute("PID"));
    vid = atoi(root_usb_id->Attribute("VID"));
    iend = atoi(root_usb_port->Attribute("IN"));
    oend = atoi(root_usb_port->Attribute("OUT"));

    iend |= 0x80;
    oend &= ~0x80;
    if(gsCmd.cmd == CMD_TYPE_WRITE)
    {
        inFile.open(filePath, ios::binary | ios::in);
        if(!inFile)
        {
            fprintf(stderr, "cannot open file:%s\n",filePath.c_str());
            return 0;
        }
        // 在写文件操作时，如果传入的数据大小为0，则指定为文件的大小
        inFile.seekg(0,ios_base::end);
        l = inFile.tellg();
        if((gsCmd.size == 0) || (gsCmd.size > l))
        {
            gsCmd.size = l;
        }
    }
    else if(gsCmd.cmd == CMD_TYPE_READ)
    {
        if(gsCmd.size == 0)
        {
            fprintf(stderr, "Error read size\n");
            return 0;
        }
        outFile.open(filePath, ios::binary | ios::out);
        if(!outFile)
        {
            fprintf(stderr, "cannot open file:%s\n",filePath.c_str());
            return 0;
        }
    }

    int res = libusb_init(0);
    if(res != 0)
    {
        fprintf(stderr, "Error libusb init\n");
        inFile.close();
        outFile.close();
        return 0;
    }

    hUsbDevice = libusb_open_device_with_vid_pid(0,vid,pid);
    if (!hUsbDevice)
    {
        fprintf(stderr, "Error open device fail.vid-%04x,pid-%04x\n",vid,pid);
        libusb_exit(0);
        inFile.close();
        outFile.close();
        return 0;
    }
    // 检查内核驱动是否连接到了这个设备，如果是，则从内核驱动断开它
    if (libusb_kernel_driver_active(hUsbDevice, 0))
    {
        res = libusb_detach_kernel_driver(hUsbDevice, 0);
        if (res == 0)
        {
            kernelDriverDetached = 1;
        }
        else if(res != LIBUSB_ERROR_NOT_SUPPORTED)
        {
            fprintf(stderr, "Error detaching kernel driver.\n");
            return 1;
        }
    }
    // 声明接口0
    res = libusb_claim_interface(hUsbDevice, 0);
    if (res != 0)
    {
        fprintf(stderr, "Error claiming interface.\n");
        libusb_exit(0);
        inFile.close();
        outFile.close();
        return 1;
    }
    //发送命令
    res = usbDevice_sendCommond(&gsCmd,infoBuffer,8);
    if(res != 0)
    {
        fprintf(stderr, "Error send commond.\n");
        libusb_exit(0);
        inFile.close();
        outFile.close();
        return 0;
    }
    // 设备一次能接收多少的数据
    deviceCacheSize = *(uint32_t *)(&infoBuffer[0]);
    // deviceCacheSize = 512;
    if(deviceCacheSize == 0)
    {
        fprintf(stderr,"Error device cache info\n");
        libusb_exit(0);
        inFile.close();
        outFile.close();
        return 0;
    }
    // 设备存储器块大小
    memBlockSize = *(uint32_t *)(&infoBuffer[4]);
    if(memBlockSize == 0)
    {
        fprintf(stderr,"Error device block size\n");
        libusb_exit(0);
        inFile.close();
        outFile.close();
        return 0;
    }
    // 设备是否有可供读写的空间
    i = *(uint32_t *)(&infoBuffer[8]);
    i *= memBlockSize;
    if(i < gsCmd.size)
    {
        fprintf(stderr,"Error device no free memory\n");
        libusb_exit(0);
        inFile.close();
        outFile.close();
        return 0;
    }
    //传输数据
    switch(gsCmd.cmd)
    {
        case CMD_TYPE_WRITE:
            i=0;
            datBuff = (uint8_t *)malloc(deviceCacheSize);
            inFile.seekg(0,ios_base::beg);
            while (i < gsCmd.size)
            {
                inFile.read((char *)datBuff,deviceCacheSize);
                l = inFile.gcount();
                res = usbDevice_sendData(datBuff,l);
                if(res != 0)
                {
                    fprintf(stderr, "Error send data.\n");
                    break;
                }
                i += l;
                fprintf(stdout, "write size:%d\n",i);
            }
            free(datBuff);
            if(i == gsCmd.size)
            {
                fprintf(stdout, "send data ok\n");
            }
            break;
        case CMD_TYPE_READ:
            datBuff = (uint8_t *)malloc(deviceCacheSize);
            len = 0 ;
            i = gsCmd.size;
            while(i)
            {
                int numBytes;
                if(i > deviceCacheSize)
                {
                    l = deviceCacheSize;
                }
                else
                {
                    l = i ;
                }
                i -= l ;
                res = libusb_bulk_transfer(hUsbDevice, iend,datBuff, l, &numBytes, 6000);
                if(res != 0)
                {
                    fprintf(stdout, "Error read data\n");
                    break;
                }
                outFile.write((const char *)datBuff,numBytes);
                len += numBytes;
                fprintf(stdout,"read size:%d\n",len);
            }
            free(datBuff);
            break;
        case CMD_TYPE_JUMP:
            break;
        default:
            break;
    }
    inFile.close();
    outFile.close();

    // 释放接口0
    res = libusb_release_interface(hUsbDevice, 0);
    if (0 != res)
    {
        fprintf(stderr, "Error releasing interface.\n");
    }
    //如果我们之前从接口＃0分离了内核驱动程序，我们现在就可以了需要再次附加它
    if (kernelDriverDetached)
    {
        libusb_attach_kernel_driver(hUsbDevice, 0);
    }
    // 关闭 libusb
    libusb_exit(0);

    return 0;
}

int usbDevice_sendCommond(_S_Commond *sCmd,unsigned char *retInfoAddr,uint32_t size)
{
    unsigned char buff[512] __attribute__ ((aligned (4))) ;

    buff[0]   = 0x55;
    buff[1]   = 0xAA;
    buff[2]   = 0x99;
    buff[3]   = 0x66;
    memcpy(&buff[4],&gsCmd,sizeof(_S_Commond));
    buff[508] = 0xAA;
    buff[509] = 0x55;
    buff[510] = 0x66;
    buff[511] = 0x99;

    int numBytes;
    int res = libusb_bulk_transfer(hUsbDevice, oend,buff, 512, &numBytes, 1000);
    if (res != 0)
    {
        fprintf(stderr, "Error sending message to device.\n");
    }
    memset(buff,0,sizeof(buff));
    res = libusb_bulk_transfer(hUsbDevice, iend, buff, 512, &numBytes, 1000);
    if (0 == res)
    {
        //这里处理数据
        uint32_t *pHead,*pEnd;
        pHead = (uint32_t *)&buff[0];
        pEnd = (uint32_t *)&buff[508];
        if(!((*pHead == ~(*pEnd)) && (*pHead == 0x6699AA55)))
        {
            fprintf(stderr, "Error return data.\n");
            return -2;
        }
        memcpy(retInfoAddr,&buff[8],size);
    }
    else
    {
        fprintf(stderr, "Error receiving message.\n");
        return -4 ;
    }
    return 0;
}

int usbDevice_sendData(unsigned char *buff,uint32_t size)
{
    int numBytes,res;
    unsigned char rb[512] __attribute__ ((aligned (4))) ;

    res = libusb_bulk_transfer(hUsbDevice, oend,buff, size, &numBytes, 3000);
    if (res != 0)
    {
        fprintf(stderr, "Error sending data to device.\n");
        return -1 ;
    }
    memset(rb,0,sizeof(rb));
    res = libusb_bulk_transfer(hUsbDevice, iend, rb, 512, &numBytes, 6000);
    if (0 == res)
    {
        //这里处理数据
        uint32_t *pHead,*pEnd;
        pHead = (uint32_t *)&rb[0];
        pEnd = (uint32_t *)&rb[508];
        if(!((*pHead == ~(*pEnd)) && (*pHead == 0x6699AA55)))
        {
            fprintf(stderr, "Error return data.\n");
            return -2;
        }
        pHead = (uint32_t *)&rb[4];
        if(*pHead != 0)//返回非0值表示有错误
        {
            return -3;
        }
    }
    else
    {
        fprintf(stderr, "Error receiving message.\n");
        return -4 ;
    }
    return 0;
}


int usbDevice_readData(unsigned char *buff,uint32_t size)
{
    int numBytes,res;

    res = libusb_bulk_transfer(hUsbDevice, iend,buff, size, &numBytes, 5000);
    if (res != 0)
    {
        fprintf(stderr, "Error sending message to device.\n");
        return -1 ;
    }

    return 0;
}
